﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

using MegavaniaX;
using MegavaniaX.World;
using MegavaniaX.Messaging.Events;

namespace MegavaniaX.Rendering
{
    /// <summary>
    /// Camera class.
    /// </summary>
    public class MegavaniaCamera : IEventListener
    {
        /// <summary>
        /// X position of the camera.
        /// </summary>
        public float gameX;

        /// <summary>
        /// Y position of the camera.
        /// </summary>
        public float gameY;

        /// <summary>
        /// Width of the camera, in PIXELS
        /// </summary>
        public int gameWidth;

        /// <summary>
        /// Height of the camera, in PIXELS
        /// </summary>
        public int gameHeight;

        /// <summary>
        /// The amount by which the camera magnifies everything.
        /// </summary>
        public float ZoomFactor;

        /// <summary>
        /// Maximum zoom in factor.
        /// </summary>
        private const float ZoomFactorMax = 6.00f;

        /// <summary>
        /// Minimum zoom out factor.
        /// </summary>
        private const float ZoomFactorMin = 0.010f;

        /// <summary>
        /// Current camera velocity.
        /// </summary>
        private Vector2 velocity;

        /// <summary>
        /// Speed at which the camera moves during screen transitions
        /// </summary>
        public const float TransitionSpeed = 0.250f;

        public float TransitionTargetX;
        public float TransitionTargetY;
        private bool IsTransitioning = false;

        /// <summary>
        /// Determines whether or not an object with the given x/y/width/height is visible inside the current viewport.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="height"></param>
        /// <param name="width"></param>
        /// <returns></returns>
        public Boolean IsInViewport(float obj_x, float obj_y, int obj_height, int obj_width)
        {
            // Strategy: check to see if any of the four corners is in the viewport, if so render
            if (   (obj_x + obj_width >= gameX && obj_y + obj_height >= gameY) // bottom-right is in screen
                || (obj_x <= gameX + gameWidth && obj_y + obj_height >= gameY) // bottom-left is in screen
                || (obj_x + obj_width >= gameX && obj_y <= gameY + gameHeight) // top-right
                || (obj_x <= gameX + gameWidth && obj_y <= gameY + gameHeight)) // top-left
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Updates the camera.
        /// </summary>
        /// <param name="elapsedTime"></param>
        public void Update(TimeSpan elapsedTime, Actor player, Screen currentScreen)
        {
            // special case: if we're transitioning we'll move our current velocity until we hit the transition targets
            if (IsTransitioning)
            {
                gameX += velocity.X * elapsedTime.Milliseconds;
                gameY += velocity.Y * elapsedTime.Milliseconds;

                if ((gameX >= TransitionTargetX && velocity.X > 0.0) || (gameX <= TransitionTargetX && velocity.X < 0.0) ||
                    (gameY >= TransitionTargetY && velocity.Y > 0.0) || (gameY <= TransitionTargetY && velocity.Y < 0.0)
                    )
                {
                    gameX = TransitionTargetX;
                    gameY = TransitionTargetY;
                    var endEvt = new ScreenTransistionEvent(MegavaniaEventType.ScreenTransistionEnd, 'T', 0, 0, null);
                    IsTransitioning = false;
                    velocity = Vector2.Zero;
                    MessagingSystem.DispatchEvent(endEvt);
                }

            }
            else
            {
                // determine if we need to move the camera based on the player's position and current screen

                // go down?
                if (player.Bottom > (this.gameY + currentScreen.TopBound + 170) && player.velocity.Y > 0.0f
                    && (gameY + 256 <= currentScreen.BottomBound))
                {
                    gameY += player.velocity.Y * elapsedTime.Milliseconds;
                    if (gameY + 256 > currentScreen.BottomBound)
                        gameY = currentScreen.BottomBound - 256;
                    //MegavaniaX.logger.DebugFormat("Setting Camera gameY to {0}", gameY);
                }

                // go right?
                if (player.Right > (this.gameX + 170) && player.velocity.X > 0.0f
                    && (gameX + 256 <= Screen.ScreenWidth * (currentScreen.OffsetX + currentScreen.Width) * 16.0f))
                {
                    gameX += player.velocity.X * elapsedTime.Milliseconds;
                    if (gameX + 256 > Screen.ScreenWidth * (currentScreen.OffsetX + currentScreen.Width) * 16.0f)
                    {
                        gameX = Screen.ScreenWidth * (currentScreen.OffsetX + currentScreen.Width) * 16.0f - 256;
                    }
                    //MegavaniaX.logger.DebugFormat("Setting Camera gameX to {0}", gameX);
                }

                // go up?
                if (player.Bottom < (this.gameY + (currentScreen.TopBound) + 60) && player.velocity.Y < 0.0f
                    && (gameY >= currentScreen.TopBound))
                {
                    gameY += player.velocity.Y * elapsedTime.Milliseconds;
                    if (gameY < currentScreen.TopBound)
                        gameY = currentScreen.TopBound;
                    //MegavaniaX.logger.DebugFormat("Setting Camera gameY to {0}", gameY);
                }

                // go left?
                if (player.Left < (this.gameX + 60) && player.velocity.X < 0.0f
                    && (gameX >= currentScreen.LeftBound))
                {
                    gameX += player.velocity.X * elapsedTime.Milliseconds;
                    if (gameX < currentScreen.LeftBound)
                        gameX = currentScreen.LeftBound;
                    //MegavaniaX.logger.DebugFormat("Setting Camera gameX to {0}", gameX);
                }
            }
        }

        /// <summary>
        /// Translates the given in-game coordinates to on-screen coordinates, based on the camera's current
        /// position and zoom.
        /// </summary>
        /// <param name="gameVector"></param>
        /// <returns></returns>
        public Vector2 GameCoordinatesToScreenCoordinates(Vector2 gameVector)
        {
            return (ZoomFactor * (new Vector2(gameVector.X - gameX, gameVector.Y - gameY)));
        }

        /// <summary>
        /// Callback for when camera starts moving.
        /// </summary>
        /// <param name="evt"></param>
        public void OnCameraMoveStart(IMegavaniaEvent evt)
        {
            var camEvent = (CameraMovementEvent)evt;

            if (evt.EventType == MegavaniaEventType.CameraMoveUpStart)
            {
                velocity.Y = camEvent.ElapsedTime.Milliseconds / 10.0f;
            }
        }

        /// <summary>
        /// Callback for when camera stops moving.
        /// </summary>
        /// <param name="evt"></param>
        public void OnCameraMoveStop(IMegavaniaEvent evt)
        {
            //velocity = Vector2.Zero;
        }

        /// <summary>
        /// Starts transitioning to the given coordinates.
        /// </summary>
        /// <param name="endX"></param>
        /// <param name="endY"></param>
        public void StartTransition(float endX, float endY, Vector2 velo)
        {
            IsTransitioning = true;
            TransitionTargetX = endX;
            TransitionTargetY = endY;
            velocity = velo;
        }

#region Event handlers

        /// <summary>
        /// 
        /// </summary>
        /// <param name="evt"></param>
        public void ChangeZoom(IMegavaniaEvent evt)
        {
            var zoomEvent = (CameraZoomEvent)evt;
            float zfc = zoomEvent.ZoomFactor;
            ZoomFactor += zfc;
            // check for boundary conditions
            if (ZoomFactor < ZoomFactorMin)
                ZoomFactor = ZoomFactorMin;
            else if (ZoomFactor > ZoomFactorMax)
                ZoomFactor = ZoomFactorMax;            
        }

#endregion

    }
}
